package com.demo.commons.config;

import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Cluster;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.lettuce.core.ClientOptions;
import io.lettuce.core.SocketOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import lombok.Data;

@Data
@Configuration
public class RedisConfiguration {

	@Autowired
	private RedisProperties redisProperties;

	@Bean(name = "redisTemplate")
	public RedisTemplate<?, ?> template() {
		RedisTemplate<?, ?> template = new RedisTemplate<>();
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
				Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		jackson2JsonRedisSerializer.setObjectMapper(om);

		template.setKeySerializer(jackson2JsonRedisSerializer);
		template.setValueSerializer(jackson2JsonRedisSerializer);
		template.setHashKeySerializer(jackson2JsonRedisSerializer);
		template.setHashValueSerializer(jackson2JsonRedisSerializer);
		LettuceConnectionFactory connectionFactory = lettuceConnectionFactory();
		connectionFactory.setValidateConnection(true); // 校验连接的有效性
		// 关闭Lettuce的共享连接
		connectionFactory.setShareNativeConnection(false);
		template.setConnectionFactory(connectionFactory);
		return template;
	}

	
	@Bean
	public LettuceConnectionFactory lettuceConnectionFactory() {
		Cluster cluster = redisProperties.getCluster();
		Sentinel sentinel = redisProperties.getSentinel();
		if (null != cluster && null != cluster.getNodes() && cluster.getNodes().size() > 0) { // cluster的nodes节点不为空，说明启用的是redis集群配置
			//集群模式
			return clusterLettuceConnectionFactory();
		}else if(null != sentinel && null != sentinel.getNodes() && sentinel.getNodes().size() > 0) {
			//哨兵模式
			RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
	        sentinelConfig.setMaster(redisProperties.getSentinel().getMaster());
	        Set<RedisNode> sentinels = new HashSet<>();
	        List<String> list = redisProperties.getSentinel().getNodes();
	        for (String redisHost : list) {
	            String[] item = redisHost.split(":");
	            String ip = item[0].trim();
	            String port = item[1].trim();
	            sentinels.add(new RedisNode(ip, Integer.parseInt(port)));
	        }
	        sentinelConfig.setSentinels(sentinels);
	        sentinelConfig.setDatabase(redisProperties.getDatabase());
	        if (StringUtils.isNotBlank(redisProperties.getPassword())) {
	        	sentinelConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
			}
	        return new LettuceConnectionFactory(sentinelConfig);
		}else { //单机模式
			RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
			configuration.setHostName(redisProperties.getHost());
			configuration.setPort(redisProperties.getPort());
			if (StringUtils.isNotBlank(redisProperties.getPassword())) {
				configuration.setPassword(RedisPassword.of(redisProperties.getPassword()));
			}
			configuration.setDatabase(redisProperties.getDatabase());
			return new LettuceConnectionFactory(configuration);
		}
		
	}

	/**
	 * 集群模式
	 * 
	 * @return
	 */
	public LettuceConnectionFactory clusterLettuceConnectionFactory() {
		// 配置用于开启自适应刷新和定时刷新。如自适应刷新不开启，Redis集群变更时将会导致连接异常
		ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
				.enablePeriodicRefresh(Duration.ofSeconds(15))// 开启周期刷新(默认60秒)
				// .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT,
				// ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS)// 开启自适应刷新
				.enableAllAdaptiveRefreshTriggers().adaptiveRefreshTriggersTimeout(Duration.ofSeconds(10)).build();
		ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
				.topologyRefreshOptions(clusterTopologyRefreshOptions)// 拓扑刷新
				.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS).autoReconnect(true)
				.socketOptions(SocketOptions.builder().keepAlive(true).build()).validateClusterNodeMembership(false)// 取消校验集群节点的成员关系
				.build();
		LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
				.clientOptions(clusterClientOptions).poolConfig(genericObjectPoolConfig()).build();
		LettuceConnectionFactory factory = new LettuceConnectionFactory(getClusterConfiguration(), clientConfig);
		return factory;
	}

	/**
	 * 构建Redis连接池
	 *
	 * @param maxIdle
	 *            最大空闲连接数 推荐20
	 * @param mixIdle
	 *            最小空闲连接数 推荐0
	 * @param maxTotal
	 *            设置最大连接数，（根据并发请求合理设置）推荐100。
	 * @return 返回一个连接池
	 */
	private GenericObjectPoolConfig genericObjectPoolConfig() {
		GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
		poolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
		poolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
		poolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());
		// 此处可添加其它配置
		return poolConfig;
	}

	private RedisClusterConfiguration getClusterConfiguration() {
		RedisClusterConfiguration config = new RedisClusterConfiguration();

		List<String> list = redisProperties.getCluster().getNodes();
		Set<RedisNode> nodes = new HashSet<RedisNode>();
		for (String ipPort : list) {
			String[] ipAndPort = ipPort.split(":");
			nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.valueOf(ipAndPort[1])));
		}
		nodes.forEach(n -> {
			config.addClusterNode(n);
		});
		if (null != redisProperties.getCluster().getMaxRedirects()) {
			config.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
		}
		if (null != redisProperties.getPassword() && !"".equals(redisProperties.getPassword())) {
			config.setPassword(RedisPassword.of(redisProperties.getPassword()));
		}
		return config;
	}

	@SuppressWarnings("rawtypes")
	@Bean
	public RedisTemplate lockerRedisTemplate(RedisTemplate redisTemplate) {
		return redisTemplate;
	}

	@Bean
	public CacheManager cacheManager() {
		RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(
				RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
				.entryTtl(Duration.ofSeconds(120));
		RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder
				.fromConnectionFactory(lettuceConnectionFactory()).cacheDefaults(configuration).build();

		return redisCacheManager;
	}

}
